#ifndef SCHEDULE_MODEL_
#define SCHEDULE_MODEL_

#include "dag_generator.hpp"
#include "processors_generator.hpp"

namespace schedule{


	template <	typename TASK_COMPUTATION_COST_GENERATOR,
				typename TASK_COMMUNICATION_COST_GENERATOR,
				typename PROCESSOR_COMPUTATION_COST_GENERATOR, 
				typename COMMUNICATION_COST_GENERATOR>

	class Model{

	public:
		typename dag::RandomGenerator< TASK_COMPUTATION_COST_GENERATOR, TASK_COMMUNICATION_COST_GENERATOR >::DirectedGraph		tasks_;
		typename processors::RandomGenerator< PROCESSOR_COMPUTATION_COST_GENERATOR, COMMUNICATION_COST_GENERATOR >::Processors	procs_;

		struct task_ext_info{
			int		processor;

			//double	start_time;
			double	finish_time;
			bool	bFinished;

			task_ext_info() : processor(0), finish_time(0.0), bFinished(false){};
		};
		std::map<int, task_ext_info>	matching_;	// task reserve id -> info

		void randome_matching(){
			matching_.clear();

			int T = num_vertices(tasks_);
			int P = static_cast<int>( procs_.computation_cost.size() );

			for ( int i=0; i<T; i++)
			{
				matching_[i].processor = rand() % P;
			}

		}

		void printMatching( const std::map<int, task_ext_info> &m ) const{
			cout << "---------- begin print matching ----------" << endl;
			std::map<int, task_ext_info>::const_iterator itr;
			for ( itr = m.begin(); itr != m.end(); ++itr)
			{
				cout << "Task " << itr->first 
					 << ": processor " << itr->second.processor
					 << " finished at " << itr->second.finish_time
					 << endl;
			}
			cout << "---------- end print matching ----------" << endl;
		};

		double get_complete_time( std::map<int, task_ext_info>* pResult = NULL ){
			int T = num_vertices(tasks_);
			int P = static_cast<int>( procs_.computation_cost.size() );

			// check size
			if ( matching_.size() != T )
			{
				cout <<"FATAL ERROR: Tasks size (" << T << ") does not equal to " 
					<< "schedule matching size (" << matching_.size() << ")" << endl;
				return 0.0;
			}

			// run task
			double complete_time = calc_task_complete_time( T-1 );

			// return result
			if (pResult){
				*pResult = matching_;
			}
			return complete_time;
		};

	protected:
		double calc_communication_time( double communication_weight, double speed){
			return communication_weight * speed;
		};

		double calc_task_complete_time(int taskid){

			if (matching_[taskid].bFinished)
			{
				return matching_[taskid].finish_time;
			}

			double computation_time =  static_cast<double>(
				tasks_[taskid].cost) / 
				procs_.computation_cost[ matching_[taskid].processor ];

			if ( taskid == 0){
				matching_[taskid].finish_time = computation_time;
			}
			else{
				std::pair<dag::RandomGenerator<TASK_COMPUTATION_COST_GENERATOR, TASK_COMMUNICATION_COST_GENERATOR>::edge_iterator, 
					dag::RandomGenerator<TASK_COMPUTATION_COST_GENERATOR, TASK_COMMUNICATION_COST_GENERATOR>::edge_iterator> p;

				double max_ready_time = 0.0;

				for( p = edges(tasks_); p.first != p.second; ++p.first ){

					if (target(*p.first, tasks_) != taskid)
					{
						continue;
					}

					double task_complete_time = calc_task_complete_time( source(*p.first, tasks_) );
					double communication_time =
						calc_communication_time( get(edge_weight, tasks_, *p.first), procs_.communicaiton_cost[source(*p.first, tasks_)][taskid]);
					
					double ready_time = 
						task_complete_time + communication_time;

					//cout << "task_complete_time: " << task_complete_time
					//	 << " communication_time: " << communication_time
					//	 << " ready_time: " << ready_time
					//	 << " for task " << taskid
					//	 << endl;


					max_ready_time = max( max_ready_time, ready_time );
				}
				
				matching_[taskid].finish_time = max_ready_time + computation_time;
			}

			matching_[taskid].bFinished = true;
			return matching_[taskid].finish_time;
		}
	};

}

#endif